package br.com.lambdacore.persistence.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.Query;

import br.com.lambdacore.model.EntityModel;

public abstract class JpaDAO<T extends EntityModel<PK>, PK extends Serializable>
		implements DAO<T, PK> {

	private static final String NAMED_QUERY_FIND_ALL = "findAll";

	private Class<T> persistentClass;

	public JpaDAO() {
		this.configurePersistentClass();
	}

	public boolean contains(T entity) {
		return this.getEntityManager().contains(entity);
	}

	public PK create(T entity) {
		this.persist(entity);
		return this.merge(entity).getId();
	}

	public void delete(T entity) {
		this.remove(entity);
	}

	public void deleteAll(T... entities) {
		this.removeAll(entities);
	}

	@SuppressWarnings("unchecked")
	public Collection<T> find(String query, Map<String, ?> parameters) {
		return this.createNamedQuery(query).getResultList();
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findAll() {
		return this.getFindAllQuery().getResultList();
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findAllFirstResult(Integer firstResult) {
		return this.getFindAllQuery().setFirstResult(firstResult)
				.getResultList();
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findAllMaxResults(Integer maxResults) {
		return this.getFindAllQuery().setMaxResults(maxResults).getResultList();
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findAllRange(Integer firstResult, Integer maxResults) {
		return this.getFindAllQuery().setFirstResult(firstResult)
				.setMaxResults(maxResults).getResultList();
	}

	public T findById(PK id) {
		return this.getEntityManager().find(this.getPersistentClass(), id);
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findFirstResult(String query,
			Map<String, ?> parameters, Integer firstResult) {
		Query createNamedQuery = this.setParametersToQuery(query, parameters);
		return createNamedQuery.setFirstResult(firstResult).getResultList();
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findMaxResults(String query,
			Map<String, ?> parameters, Integer maxResults) {
		Query createNamedQuery = this.setParametersToQuery(query, parameters);
		return createNamedQuery.setMaxResults(maxResults).getResultList();
	}

	@SuppressWarnings("unchecked")
	public Collection<T> findRange(String query, Map<String, ?> parameters,
			Integer firstResult, Integer maxResults) {
		Query createNamedQuery = this.setParametersToQuery(query, parameters);
		return createNamedQuery.setFirstResult(firstResult).setMaxResults(
				maxResults).getResultList();
	}

	@SuppressWarnings("unchecked")
	public T findSingleResult(String query, Map<String, ?> parameters) {
		Query createNamedQuery = this.setParametersToQuery(query, parameters);
		return (T) createNamedQuery.getSingleResult();
	}

	public T getReference(T entity) {
		return this.getEntityManager().getReference(this.getPersistentClass(),
				entity);
	}

	public void insert(T entity) {
		this.persist(entity);
	}

	public void lock(T entity, LockModeType lockModeType) {
		this.getEntityManager().lock(entity, lockModeType);
	}

	public void lockRead(T entity) {
		this.lock(entity, LockModeType.READ);
	}

	public void lockWrite(T entity) {
		this.lock(entity, LockModeType.WRITE);
	}

	public T merge(T entity) {
		return this.getEntityManager().merge(entity);
	}

	public void persist(T entity) {
		this.getEntityManager().persist(entity);
	}

	public void refresh(T entity) {
		this.getEntityManager().refresh(entity);
	}

	public void remove(T entity) {
		this.getEntityManager().remove(entity);
	}

	public void removeAll(T... entities) {
		for (T entity : entities) {
			this.remove(entity);
		}
	}

	public T update(T entity) {
		return this.merge(entity);
	}

	protected abstract EntityManager getEntityManager();

	protected Class<T> getPersistentClass() {
		return this.persistentClass;
	}

	@SuppressWarnings("unchecked")
	private void configurePersistentClass() {
		this.setPersistentClass((Class<T>) ((ParameterizedType) this.getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0]);
	}

	private Query createNamedQuery(String query) {
		return this.getEntityManager().createNamedQuery(query);
	}

	private Query getFindAllQuery() {
		return this.createNamedQuery(NAMED_QUERY_FIND_ALL);
	}

	private Query setParametersToQuery(String query, Map<String, ?> parameters) {
		Query createNamedQuery = this.createNamedQuery(query);
		for (Entry<String, ?> entry : parameters.entrySet()) {
			createNamedQuery.setParameter(entry.getKey(), entry.getValue());
		}
		return createNamedQuery;
	}

	private void setPersistentClass(Class<T> persistentClass) {
		this.persistentClass = persistentClass;
	}

}
